package net.larsan.dconf.cli.comm;

import java.text.ParseException;
import java.util.concurrent.atomic.AtomicInteger;

import net.larsan.dconf.DNode;
import net.larsan.dconf.DNodeStore;
import net.larsan.dconf.cli.CliModule;
import net.larsan.dconf.cli.Connector;
import net.larsan.dconf.cli.ConnectorListener;
import net.larsan.dconf.jbc.JBCPreferencesFactory;
import net.larsan.dconf.jbc.ModuleAggregate;

import org.apache.commons.cli.CommandLine;
import org.mockito.Mockito;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;

import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.util.Modules;

public abstract class CommandTestBase {

	private final AtomicInteger postExecs = new AtomicInteger();
	
	@Inject
	protected TestInputOuputHandler io;

	@Inject
	private DNodeStore store;
	
	@Inject
	private Connector conn;
	
	@BeforeClass
	public void init() {
		// TODO Make these props options
		System.setProperty("java.net.preferIPv4Stack", "true");
		System.setProperty("java.util.prefs.PreferencesFactory", JBCPreferencesFactory.class.getName());
		// Create module and inject members on "this"
		Module module = ModuleAggregate.append(Modules.override(new CliModule()).with(new TestModule()));
		Injector inj = Guice.createInjector(module);
		inj.injectMembers(this);
		startThread();
		startListen();
	}

	@AfterClass
	public void destroy() {
		feed("exit", false);
	}
	
	@BeforeMethod
	public void clear() throws ParseException {
		feed("gt root:/", false);
		for (DNode root : store.getDomainNodes()) {
			root.remove();
		}
		postExecs.set(0);
		io.clear();
	}
	
	protected String feed(String command, boolean ret) {
		if(ret) {
			return io.feed(command, ret);
		} else {
			io.feed(command, ret);
			waitForExec();
			return null;
		}
	}
	
	protected void waitForExec() {
		long millis = System.currentTimeMillis() + 1000;
		while(postExecs.get() == 0 && System.currentTimeMillis() < millis) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}
		}
	}
	
	
	// --- PRIVATE METHODS --- //
	
	private void startListen() {
		conn.addConnectorListener(new ConnectorListener() {
			
			@Override
			public void preExecute(String command) { }
			
			@Override
			public void postExecute(String command) {
				postExecs.incrementAndGet();
			}
		});
	}
	
	private void startThread() {
		Thread th = new Thread(new Exec(), "Connector Main Thread");
		th.setDaemon(true);
		th.start();
	}
	
	// --- PRIVATE CLASSES --- //
	
	private class Exec implements Runnable {
		
		private CommandLine line;
		
		private Exec() {
			line = Mockito.mock(CommandLine.class);
		}
		
		@Override
		public void run() {
			conn.exec(line);
		}
	}
}
